前兩天已經介紹過基本的 Web 知識與安裝好相關環境了,接下來我們就使用 golang 原生提供的 net/http
package 來建立一個簡單的網頁吧!
我們預計建立一個簡單的網站,網站要符合以下幾個條件
http://127.0.0.1:8888
/
或是 /index
的時候存取首頁html
格式目標設定好就讓我們開始吧!
透過 golang 建立網頁程式一點都不能,就讓我們一步一步的進行吧!
在最一開始,我們 import 兩個 package 分別為 log
與 net/http
import (
"log"
"net/http"
)
log
就是用來輸出程式目前執行狀態的,log 的輸出可以分成不同的等級,這個我們後續會特別抽出來談net/http
就是用來將網頁運行起來的關鍵,包含伺服器監聽與運行的方法與 request handler 都使用此 package 來實作在 test
這個方法中我們可以看到有兩個參數分別為 http.ResponseWriter
與 http.Request
,這兩個參數分別對應到我們昨天所提到的 response
與 request
func test(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`my first website`))
}
這個範例中,我們在方法中寫了兩行
header
寫入了 http.StatusOK
這個值,這個數值也是 net/http
這個 package 所提供的,他其實就是把 status code
給封裝起來,讓我們比較方便呼叫,因此,http.StatusOK
就是等於 200
response
的內容,回傳的型態是 []byte
,因此我們寫入 []byte(`my first website`)
routing 的部分在此範例中我們寫在 main
方法中,範例如下
http.HandleFunc("/", test)
我們實作了 net/http
package 中的 HandleFunc
方法,將上一部所寫的 test
方法與 /
這個 routing 進行綁定,讓 server 知道當進來的 traffic 的 routing 為 /
時要執行 test
方法
直接實作 net/http
中提供的 ListenAndServe
方法,一樣是寫在 main
方法中
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
ListenAndServe
有兩個參數分別為 address
與 handler
address
為存取的 url
與 port
,因為沒有指定 url
所以沒有填寫,只單純寫 port
handler
,因此將它填寫為 nil
,綜合上述步驟程式碼最終會像是這樣
package main
import (
"log"
"net/http"
)
func test(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`my first website`))
}
func main() {
http.HandleFunc("/", test)
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
將程式碼儲存成 main.go
後,在 command line
中透過 go run main.go
指令將程式運行起來後,會看到以下畫面,程式會進入一個無窮迴圈不會停止,這樣就對了!
之後在瀏覽器輸入 http://127.0.0.1:8888
看到以下畫面就代表我們第一支網頁程式就完成拉!
你心裡一定想說「什麼?!就這麼簡單嗎?今天就這麼結束了嗎!」
想得太美了!我們今天的目標還沒達成呢!
接下來我們會製作 html 畫面
寫完上面的第一支程式之後,想必大家對網頁開發有一定的感覺了,因此我們就順著這個感覺走,在製作首頁與登入頁的部分就是兩個方向
html
畫面routing
的部分加入 /index
並綁定 request handler
html
畫面要回傳 html 還不簡單,只要在 response 中寫入 html 就可以了吧!
修改上面的範例如下
func test(w http.ResponseWriter, r *http.Request) {
str := `<!DOCTYPE html>
<html>
<head><title>首頁</title></head>
<body><h1>首頁</h1><p>我的第一個首頁</p></body>
</html>
`
w.Write([]byte(str))
}
重新運行 go run main.go
後存取 http://127.0.0.1:8888
會出現以下的畫面
是的沒錯,這樣的確是成功的顯示 html 的網頁,但每次要修改 html 的時候就要重新修改程式,html 的內容也不能是動態的!
因此,我們可以使用 html/template
package 來解決這個問題!
首先,我們先定義好 template 內所要的參數
type IndexData struct {
Title string
Content string
}
我們可以在 import 的部分將 html/template
進行導入
import (
"log"
"html/template"
"net/http"
)
之後將原先的 html 內容抽出來,建立與放入與 main.go
同一個目錄底下名為 index.html
的檔案,{{}}
內的參數對應到上方 IndexData
結構內的變數
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body><h1>{{.Title}}</h1><p>{{.Content}}</p></body>
</html>
最後我們就可以修改原先的 request handler 如下
func test(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("./index.html"))
data := new(IndexData)
data.Title = "首頁"
data.Content = "我的第一個首頁"
tmpl.Execute(w, data)
}
完成後程式碼如下
package main
import (
"html/template"
"log"
"net/http"
)
type IndexData struct {
Title string
Content string
}
func test(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("./index.html"))
data := new(IndexData)
data.Title = "首頁"
data.Content = "我的第一個首頁"
tmpl.Execute(w, data)
}
func main() {
http.HandleFunc("/", test)
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
重新運行 go run main.go
後存取 http://127.0.0.1:8888
的畫面與上方相同,但這樣的寫法更為動態,不但讓我們可以單獨維護 html
檔案,也可以在 html 內動態放入程式產出的資料,這個設計真的讚讚。
routing
的部分加入 /index
並綁定相關的 request handler
這一步就相對簡單很多了,基本上就是再實作一次 HandleFunc
方法,將 /index
與 test
方法進行綁定即可
http.HandleFunc("/index", test)
最終程式如下
package main
import (
"html/template"
"log"
"net/http"
)
type IndexData struct {
Title string
Content string
}
func test(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("./index.html"))
data := new(IndexData)
data.Title = "首頁"
data.Content = "我的第一個首頁"
tmpl.Execute(w, data)
}
func main() {
http.HandleFunc("/", test)
http.HandleFunc("/index", test)
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
今天主要就是透過 golang 內建的 package 建立基本的網頁,大家可以發現 golang 非常適合寫網頁,整體編譯與回應的速度都令人滿意,最重要的是不用關聯到外來的 package 這件事情就贏了!
雖然原生已經有提供相關的 package,但要建立一個完整的網站需要考慮更多的情況,例如 middleware
、response 的格式
、request 資料驗證
等等,這些東西當然我們可以自己處理,但是如果有現成幫我們做好的 framework
就更好了,因此,明天就讓我們來介紹 gin
這個 web framework 吧!
你好,
我按照你的方式做
routing 的部分加入底下三個
http.HandleFunc("/", test)
http.HandleFunc("/index", test)
http.HandleFunc("/p1", p1)
跟一個p1函數
func p1(w http.ResponseWriter, r *http.Request) {
currentTime:=time.Now()
fmt.Println("p1============")
fmt.Println(currentTime)
tmpl := template.Must(template.ParseFiles("./p1.html"))
data := new(IndexData)
data.Title = "頁1"
data.Content = "我的第一頁"
tmpl.Execute(w, data)
}
不知道是否.我理解錯誤???
瀏覽器上輸入 http://127.0.0.1:8888/ ,應該是執行test函數
瀏覽器上輸入 http://127.0.0.1:8888/index ,應該是執行test函數
瀏覽器上輸入 http://127.0.0.1:8888/p1 ,應該是執行p1函數
可是 瀏覽器上輸入 http://127.0.0.1:8888/p1,執行test及p1函數
瀏覽器上輸入 http://127.0.0.1:8888/p1
不知道為何會多了,index============
終端機顯示
main.go ,完整程式碼
package main
import (
"time"
"fmt"
"html/template"
"log"
"net/http"
)
type IndexData struct {
Title string
Content string
}
func test(w http.ResponseWriter, r *http.Request) {
currentTime:=time.Now()
fmt.Println("index============")
fmt.Println(currentTime)
tmpl := template.Must(template.ParseFiles("./index.html"))
data := new(IndexData)
data.Title = "首頁"
data.Content = "我的第一個首頁"
tmpl.Execute(w, data)
}
func p1(w http.ResponseWriter, r *http.Request) {
currentTime:=time.Now()
fmt.Println("p1============")
fmt.Println(currentTime)
tmpl := template.Must(template.ParseFiles("./p1.html"))
data := new(IndexData)
data.Title = "頁1"
data.Content = "我的第一頁"
tmpl.Execute(w, data)
}
func main() {
http.HandleFunc("/", test)
http.HandleFunc("/index", test)
http.HandleFunc("/p1", p1)
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
index.html
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body><h1>{{.Title}}</h1><p>{{.Content}}</p></body>
</html>
p1.html
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body><h1>{{.Title}}</h1><p>{{.Content}}</p></body>
</html>
不知道你是否解決了問題,但我看到後覺得有趣所以研究了一下。
如果使用terminal curl http://127.0.0.1:8888 的話就會正常的只出現一次test, 所以排除net/http本身的功能問題。
google後發現一篇文章
https://studygolang.com/topics/5126
(註:是大陸的網站,如果擔心可以不點擊
裡面有人提到如果你是使用chrome瀏覽器的話,chrome默認會請求favicon.ico,所以main.go中func test會被執行兩次,其中一次就是chrome請求favicon.ico
謝謝..難怪會發生 取兩次的問題
想請問,為何我的 localhost:8080/ 會出現 404 found 顯示找不到伺服器
這是我的程式碼